"""
WebSocket消费者模块
处理实时数据的订阅和推送
"""

import json
import logging
import asyncio
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from .models import OPCUASubscription, MQTTSubscription
from .services import DataSourceService

logger = logging.getLogger(__name__)


class SubscriptionConsumer(AsyncWebsocketConsumer):
    """订阅数据WebSocket消费者
    
    处理：
    1. WebSocket连接的建立和断开
    2. 订阅数据的实时推送
    3. 数据源连接状态的管理
    """

    async def connect(self):
        """处理WebSocket连接"""
        if self.scope["user"].is_anonymous:
            await self.close()
            return

        self.user = self.scope["user"]
        self.group_name = f"user_{self.user.id}_subscriptions"

        # 加入用户订阅组
        await self.channel_layer.group_add(
            self.group_name,
            self.channel_name
        )
        await self.accept()
        logger.info(f"WebSocket连接已建立 - 用户: {self.user.username}")

        # 获取用户的所有活跃订阅
        await self.load_subscriptions()

        # 建立数据源连接
        await self.connect_to_datasources()

    async def disconnect(self, close_code):
        """处理WebSocket断开连接"""
        logger.info(f"WebSocket连接断开 - 用户: {self.user.username}")
        # 断开数据源连接
        await self.disconnect_from_datasources()

        # 离开用户订阅组
        await self.channel_layer.group_discard(
            self.group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        """处理接收到的WebSocket消息"""
        try:
            data = json.loads(text_data)
            message_type = data.get('type')

            if message_type == 'subscribe':
                # 处理新的订阅请求
                await self.handle_subscribe(data)
            elif message_type == 'unsubscribe':
                # 处理取消订阅请求
                await self.handle_unsubscribe(data)
        except json.JSONDecodeError:
            pass

    async def subscription_data(self, event):
        """处理订阅数据更新"""
        logger.debug(f"接收到数据更新 - 用户: {self.user.username}, 数据: {event['data']}")
        await self.send(text_data=json.dumps({
            'type': 'subscription_data',
            'data': event['data']
        }))

    @database_sync_to_async
    def load_subscriptions(self):
        """加载用户的所有活跃订阅"""
        # 加载OPC UA订阅
        self.opcua_subscriptions = list(OPCUASubscription.objects.filter(
            created_by=self.user,
            is_active=True
        ).select_related('data_source'))

        # 加载MQTT订阅
        self.mqtt_subscriptions = list(MQTTSubscription.objects.filter(
            created_by=self.user,
            is_active=True
        ).select_related('data_source').prefetch_related('mqttsubscriptionitem_set'))

        logger.info(
            f"已加载订阅 - 用户: {self.user.username}, OPC UA: {len(self.opcua_subscriptions)}, MQTT: {len(self.mqtt_subscriptions)}")

    async def connect_to_datasources(self):
        """建立与数据源的连接"""
        # 连接到OPC UA数据源
        for subscription in self.opcua_subscriptions:
            await self.connect_opcua(subscription)

        # 连接到MQTT数据源
        for subscription in self.mqtt_subscriptions:
            await self.connect_mqtt(subscription)

    async def disconnect_from_datasources(self):
        """断开与数据源的连接"""
        # 断开MQTT连接
        for subscription in self.mqtt_subscriptions:
            await self.disconnect_mqtt(subscription)

        # 断开OPC UA连接
        for subscription in self.opcua_subscriptions:
            await self.disconnect_opcua(subscription)

    async def connect_opcua(self, subscription):
        """连接到OPC UA数据源并订阅节点"""
        try:
            # 使用DataSourceService建立连接
            client = await DataSourceService.connect_opcua(
                subscription.data_source.connection_string
            )

            # 创建订阅
            handler = await client.create_subscription(500, self)
            # 订阅节点
            node = client.get_node(subscription.node_id)
            await handler.subscribe_data_change(node)

            # 更新状态为已连接
            await self.send(json.dumps({
                'type': 'status_update',
                'subscription_type': 'opcua',
                'subscription_id': subscription.id,
                'status': 'connected'
            }))

            logger.info(f"OPC UA订阅已建立 - 用户: {self.user.username}, 节点: {subscription.node_id}")

        except Exception as e:
            logger.error(f"OPC UA订阅失败 - 用户: {self.user.username}, 节点: {subscription.node_id}, 错误: {str(e)}",
                         exc_info=True)
            # 更新状态为连接失败
            await self.send(json.dumps({
                'type': 'status_update',
                'subscription_type': 'opcua',
                'subscription_id': subscription.id,
                'status': 'error',
                'message': str(e)
            }))

    async def connect_mqtt(self, subscription):
        """连接到MQTT数据源并订阅主题"""
        try:
            logger.info(f"开始MQTT订阅 - 用户: {self.user.username}, 主题: {subscription.topic}")

            # 使用DataSourceService建立连接
            client = await DataSourceService.connect_mqtt(
                subscription.data_source.connection_string
            )

            # 定义消息处理回调
            async def message_callback(topic, payload):
                await self.handle_mqtt_data(topic, payload)

            # 订阅主题
            await client.subscribe(subscription.topic, message_callback)

            # 更新所有相关监控项的状态为已连接
            subscription_items = await database_sync_to_async(list)(
                subscription.mqttsubscriptionitem_set.all()
            )
            
            for item in subscription_items:
                await self.send(json.dumps({
                    'type': 'status_update',
                    'subscription_type': 'mqtt',
                    'subscription_id': subscription.id,
                    'item_id': item.id,
                    'status': 'connected'
                }))

            logger.info(f"MQTT订阅已建立 - 用户: {self.user.username}, 主题: {subscription.topic}")

        except Exception as e:
            logger.error(f"MQTT订阅失败 - 用户: {self.user.username}, 主题: {subscription.topic}, 错误: {str(e)}",
                         exc_info=True)
            # 更新所有相关监控项的状态为错误
            subscription_items = await database_sync_to_async(list)(
                subscription.mqttsubscriptionitem_set.all()
            )
            
            for item in subscription_items:
                await self.send(json.dumps({
                    'type': 'status_update',
                    'subscription_type': 'mqtt',
                    'subscription_id': subscription.id,
                    'item_id': item.id,
                    'status': 'error',
                    'message': str(e)
                }))

    async def disconnect_mqtt(self, subscription):
        """断开MQTT连接"""
        try:
            if subscription.data_source.connection_string in DataSourceService._mqtt_clients:
                client = DataSourceService._mqtt_clients[subscription.data_source.connection_string]
                await client.disconnect()
                del DataSourceService._mqtt_clients[subscription.data_source.connection_string]
                logger.info(f"MQTT连接已断开 - 用户: {self.user.username}, 主题: {subscription.topic}")
        except Exception as e:
            logger.error(f"断开MQTT连接失败 - 用户: {self.user.username}, 主题: {subscription.topic}, 错误: {str(e)}",
                         exc_info=True)

    async def handle_opcua_data(self, node_id, value, timestamp):
        """处理OPC UA数据
        
        检查是否满足报警条件，如果满足则创建报警记录
        """
        try:
            # 查找相关的订阅
            subscriptions = await database_sync_to_async(list)(
                OPCUASubscription.objects.filter(
                    node_id=node_id,
                    is_active=True
                ).select_related('data_source')
            )

            for subscription in subscriptions:
                # 发送数据更新到channel layer
                await self.channel_layer.group_send(
                    self.group_name,
                    {
                        'type': 'subscription_data',
                        'data': {
                            'type': 'data_update',
                            'subscription_type': 'opcua',
                            'subscription_id': subscription.id,
                            'value': value,
                            'timestamp': timestamp.isoformat() if timestamp else None
                        }
                    }
                )

                # 检查报警条件
                if await self.check_alarm_condition(subscription.condition, value,
                                                    subscription.threshold_value,
                                                    subscription.threshold_max):
                    # 创建报警记录
                    alarm = await self.create_opcua_alarm_record(
                        subscription=subscription,
                        value=value,
                        message=subscription.alert_message.format(value=value),
                    )

                    # 发送报警更新到channel layer
                    await self.channel_layer.group_send(
                        self.group_name,
                        {
                            'type': 'subscription_data',
                            'data': {
                                'type': 'alarm_update',
                                'subscription_type': 'opcua',
                                'subscription_id': subscription.id,
                                'alarm_id': alarm.id,
                                'message': subscription.alert_message.format(value=value),
                                'value': value,
                                'timestamp': timestamp.isoformat() if timestamp else None
                            }
                        }
                    )

                    logger.info(
                        f'OPC UA报警触发 - 订阅: {subscription.name}, '
                        f'节点: {node_id}, 值: {value}'
                    )

        except Exception as e:
            logger.error(f'处理OPC UA数据失败 - 节点: {node_id}, 错误: {str(e)}', exc_info=True)

    async def handle_mqtt_data(self, topic, payload):
        """处理MQTT数据
        
        检查是否满足报警条件，如果满足则创建报警记录
        """
        try:
            # 查找相关的订阅
            subscriptions = await database_sync_to_async(list)(
                MQTTSubscription.objects.filter(
                    topic=topic,
                    is_active=True
                ).select_related('data_source').prefetch_related('mqttsubscriptionitem_set')
            )

            for subscription in subscriptions:
                # 解析JSON数据
                try:
                    data = json.loads(payload)
                except json.JSONDecodeError:
                    logger.error(f'MQTT数据解析失败 - 主题: {topic}, 数据: {payload}')
                    continue

                # 获取所有启用状态的监控项
                items = await database_sync_to_async(list)(
                    subscription.mqttsubscriptionitem_set.filter(is_active=True)
                )

                # 检查每个启用状态的监控项
                for item in items:
                    try:
                        # 从JSON数据中获取值
                        value = self.get_value_from_json(data, item.values_id)
                        if value is None:
                            continue

                        # 获取时间戳
                        timestamp = None
                        for value_data in data.get('values', []):
                            if value_data.get('id') == item.values_id:
                                timestamp = value_data.get('t')
                                break

                        # 发送数据更新到channel layer
                        await self.channel_layer.group_send(
                            self.group_name,
                            {
                                'type': 'subscription_data',
                                'data': {
                                    'type': 'data_update',
                                    'subscription_type': 'mqtt',
                                    'subscription_id': subscription.id,
                                    'item_id': item.id,
                                    'value': value,
                                    'timestamp': timestamp
                                }
                            }
                        )

                        # 检查报警条件
                        if await self.check_alarm_condition(item.condition, value,
                                                            item.threshold_value,
                                                            item.threshold_max):
                            # 创建报警记录
                            alarm = await self.create_mqtt_alarm_record(
                                subscription=subscription,
                                item=item,
                                value=value,
                                message=item.alert_message.format(value=value)
                            )

                            # 发送报警更新到channel layer
                            await self.channel_layer.group_send(
                                self.group_name,
                                {
                                    'type': 'subscription_data',
                                    'data': {
                                        'type': 'alarm_update',
                                        'subscription_type': 'mqtt',
                                        'subscription_id': subscription.id,
                                        'item_id': item.id,
                                        'alarm_id': alarm.id,
                                        'message': item.alert_message.format(value=value),
                                        'value': value,
                                        'timestamp': timestamp
                                    }
                                }
                            )

                            logger.info(
                                f'MQTT报警触发 - 订阅: {subscription.topic}, '
                                f'监控项: {item.name}, 值: {value}'
                            )

                    except Exception as e:
                        logger.error(
                            f'处理MQTT监控项失败 - 主题: {topic}, '
                            f'监控项: {item.values_id}, 错误: {str(e)}',
                            exc_info=True
                        )
                        continue

        except Exception as e:
            logger.error(f'处理MQTT数据失败 - 主题: {topic}, 错误: {str(e)}', exc_info=True)

    @database_sync_to_async
    def create_mqtt_alarm_record(self, subscription, item, value, message):
        """创建MQTT报警记录
        
        Args:
            subscription: MQTT订阅对象
            item: MQTT订阅监控项对象
            value: 触发报警的值
            message: 报警消息
        """
        from alarms.models import AlarmRecord
        return AlarmRecord.objects.create(
            subscription_type='mqtt',
            mqtt_subscription=subscription,
            mqtt_item=item,
            value=value,
            message=message,
            name=item.name
        )

    @database_sync_to_async
    def create_opcua_alarm_record(self, subscription, value, message):
        """创建OPC UA报警记录
        
        Args:
            subscription: OPC UA订阅对象
            value: 触发报警的值
            message: 报警消息
        """
        from alarms.models import AlarmRecord
        return AlarmRecord.objects.create(
            subscription_type='opcua',
            opcua_subscription=subscription,
            value=value,
            message=message,
            name=subscription.name
        )

    async def disconnect_opcua(self, subscription):
        """断开OPC UA连接"""
        try:
            if subscription.data_source.connection_string in DataSourceService._opcua_clients:
                client = DataSourceService._opcua_clients[subscription.data_source.connection_string]
                try:
                    await client.disconnect()
                except Exception as e:
                    # 忽略会话失效的错误，因为这是正常的
                    if 'BadSessionIdInvalid' not in str(e):
                        logger.error(
                            f"断开OPC UA连接失败 - 用户: {self.user.username}, 节点: {subscription.node_id}, 错误: {str(e)}",
                            exc_info=True)

                # 无论如何都要清理客户端引用
                del DataSourceService._opcua_clients[subscription.data_source.connection_string]
                logger.info(f"OPC UA连接已断开 - 用户: {self.user.username}, 节点: {subscription.node_id}")
        except Exception as e:
            logger.error(
                f"清理OPC UA连接失败 - 用户: {self.user.username}, 节点: {subscription.node_id}, 错误: {str(e)}",
                exc_info=True)

    # OPC UA订阅处理器接口方法
    async def datachange_notification(self, node_id, val, data):
        """OPC UA数据变化通知"""
        try:
            # 获取时间戳
            timestamp = None
            if hasattr(data, 'MonitoredItems') and data.MonitoredItems:
                monitored_item = data.MonitoredItems[0]
                if hasattr(monitored_item, 'Value'):
                    timestamp = monitored_item.Value.SourceTimestamp or monitored_item.Value.ServerTimestamp

            # 如果无法获取时间戳，使用当前时间
            if timestamp is None:
                import datetime
                timestamp = datetime.datetime.now(datetime.timezone.utc)

            # 创建任务处理数据
            asyncio.create_task(self.handle_opcua_data(node_id, val, timestamp))
        except Exception as e:
            logger.error(f"处理OPC UA数据变化通知失败: {str(e)}", exc_info=True)

    def event_notification(self, event):
        """OPC UA事件通知"""
        pass  # 暂不处理事件

    def status_change_notification(self, status):
        """OPC UA状态变化通知"""
        pass  # 暂不处理状态变化

    async def check_alarm_condition(self, condition, value, threshold_value, threshold_max=None):
        """检查是否满足报警条件
        
        Args:
            condition: 条件类型 ('gt', 'lt', 'eq', 'range')
            value: 当前值
            threshold_value: 阈值
            threshold_max: 最大阈值（仅范围条件使用）
            
        Returns:
            bool: 是否满足报警条件
        """
        try:
            if condition == 'gt':
                return value > threshold_value
            elif condition == 'lt':
                return value < threshold_value
            elif condition == 'eq':
                return value == threshold_value
            elif condition == 'range':
                return threshold_value <= value <= threshold_max
            return False
        except Exception as e:
            logger.error(f'检查报警条件失败: {str(e)}', exc_info=True)
            return False

    def get_value_from_json(self, data, values_id):
        """从JSON数据中获取指定ID的值
        
        Args:
            data: JSON数据
            values_id: 值的ID
            
        Returns:
            float: 获取到的值，如果未找到返回None
        """
        try:
            # 遍历values数组
            for value_data in data.get('values', []):
                if value_data.get('id') == values_id:
                    return float(value_data.get('v', 0))
            return None
        except Exception as e:
            logger.error(f'从JSON获取值失败 - ID: {values_id}, 错误: {str(e)}', exc_info=True)
            return None
